package cn.dansj.common.utils.spring.advisor;

import cn.dansj.common.utils.lang.NotNull;
import cn.dansj.common.utils.lang.Nullable;
import cn.dansj.common.utils.spring.AnnotationUtils;
import cn.dansj.common.utils.transfer.Transformation;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.Order;

import java.lang.reflect.Method;

public abstract class AbstractBeanMethodFactoryPointcutAdvisor extends AbstractBeanFactoryPointcutAdvisor implements MethodInterceptor, BeanPostProcessor {
    @Nullable
    private Integer order;

    public void setOrder(int order) {
        this.order = order;
    }

    private final Pointcut pointcut = new StaticMethodMatcherPointcut() {
        @Override
        public boolean matches(@NotNull Method method, @NotNull Class<?> targetClass) {
            return match(method, targetClass);
        }
    };

    public abstract boolean match(Method method, Class<?> targetClass);

    public int getOrder() {
        return Transformation.requireNonNullElseGet(this.order, () -> AnnotationUtils.findMergedAnnotation(getClass(), Order.class).value());
    }

    @Override
    @NotNull
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    @NotNull
    public Advice getAdvice() {
        return this;
    }

    public abstract Object execute(MethodInvocation invocation) throws Throwable;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //fix 使用其他aop组件时,aop切了两次.
        final Object referThis = invocation.getThis();
        if (referThis != null && !AopProxyUtils.ultimateTargetClass(referThis).equals(referThis.getClass())) {
            return invocation.proceed();
        }
        return execute(invocation);
    }
}